﻿//#define DEBUG_SHOW_CREATION

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Drawing; //for Color class

namespace DirectX_CS
{
    public delegate double PolarRT_ZFunc(double radius, double theta);

	public class PolarWaveform : IManagedModelTransformable, IManagedBuffers, IManagedIndexBuffer, 
		IManagedVertexBuffer, IManagedMaterial, IColorable, IRangeModifiable,
		IDisposable
    {
		[PGBrowsable]
		public Range3d Range
		{
			get { return new Range3d(XMin, YMin, ZMin, XMax, YMax, ZMax); }
		}
		
		protected bool mEnableCallRenderPrimitives = true;
		[PGBrowsable]
		public bool EnableCallRenderPrimitives
		{
			get { return mEnableCallRenderPrimitives; }
			set { mEnableCallRenderPrimitives = value; }
		}
		protected bool mEnableCallRenderIndexedPrimitives = true;
		[PGBrowsable]
		public bool EnableCallRenderIndexedPrimitives
		{
			get { return mEnableCallRenderIndexedPrimitives; }
			set { mEnableCallRenderIndexedPrimitives = value; }
		}
        protected Device mDevice = null;

        protected bool mRegistered = false;
        protected PrimitiveManager renderer = null; //main memory manager..change name.

        public event VBChangedDelegate VertexBufferChanged;
        public event IBChangedDelegate IndexBufferChanged;

        protected Material mMaterial;
        Color mMaterialColor = Color.Red;
		Color mVertexColor = Color.PowderBlue;
		[PGBrowsable]
		public Color MainVertexColor
		{
			get { return mVertexColor; }
            set
            {
                mVertexColor = value; 
            }
		}
		private TransformMatrix modelView = new TransformMatrix();
		[PGBrowsable]
		public TransformMatrix ModelView
		{
			get { return modelView; }
			set { modelView = value; }
		}


        protected SVector[] mVertexData = null;
        SVector[,] mVertexMatrix = null;
        private int[] mIndexData = null;
		[PGBrowsable]
		public VertexFormats VertexFormat
		{
			get { return SVector.Format; }
		}

        PolarRT_ZFunc mZofR = null;
		[PGBrowsable]
        public PolarRT_ZFunc ZofR
        {
            get { return mZofR; }
            set { mZofR = value; }
        }
        private double tMax = 2*Math.PI; 
        private double tMin = 0;
		[PGBrowsable]
        public double TMin
        {
            get { return tMin; }
            set
            {
                tMin = value;
            }
        }
		[PGBrowsable]
        public double TMax
        {
            get { return tMax; }
            set
            {
                tMax = value;
            }
        }
        private double rMin = 0; //try something greater than 0 as a start, when done set back to zero.
		[PGBrowsable]
		public double RMin
        {
            get { return rMin; }
            set
            {
                rMin = value;
            }
        }
        private double rMax = 10; //set to 10 now, when done set to a function of time.
		[PGBrowsable]
		public double RMax
        {
            get { return rMax; }
            set
            {
                rMax = value;
            }
        }
        private double angleMin = 0;
		[PGBrowsable]
		public double AngleMin
        {
            get { return angleMin; }
            set
            {
                angleMin = value; 
            }
        }
        private double angleMax = 2 * Math.PI;
		[PGBrowsable]
		public double AngleMax
        {
            get { return angleMax; }
            set
            {
                angleMax = value; 
            }
        }
        private int angleDivisions = 40;
		[PGBrowsable]
		public int AngleDivisions
        {
            get { return angleDivisions; }
            set
            {
                angleDivisions = value; 
            }
        }
        private int radialDivisions = 40;
		[PGBrowsable]
		public int RadialDivisions
        {
            get { return radialDivisions; }
            set
            {
                radialDivisions = value;
            }
        }

        private double mAmp = 1;
		[PGBrowsable]
        public double Amplitude
        {
            get { return mAmp; }
            set
            {
                mAmp = value; 
            }
        }

        public double ZOfRTheta0(double radius, double theta)
        {
            double fractionTDone = (radius - RMin) / (RMax - RMin);
			//double fractionTDone = 1 - (radius - RMin) / (RMax - RMin);
            double t = fractionTDone * (TMax - TMin) + TMin;

            //double z = mSurface.Amplitude * Math.Cos(t);
            double sint = Math.Sin(t);
            double scale = 1 - fractionTDone * (1.0);
            double z = Amplitude * sint * (scale);// *cost + cost;
            return z;
        }
		public float sqrt2PI = (float)Math.Sqrt(2 * Math.PI);

		public double ZOfRThetaNormal(double radius, double theta)
		{
			double fractionTDone = (radius - RMin) / (RMax - RMin);
			//double fractionTDone = 1 - (radius - RMin) / (RMax - RMin);
			double t = fractionTDone * (TMax - TMin) + TMin;

			//double z = mSurface.Amplitude * Math.Cos(t);
			double sint = Math.Sin(t);
			float scale = (float)Math.Exp(-fractionTDone * fractionTDone / 2) / sqrt2PI;
			double z = Amplitude * sint * scale;// *cost + cost;
			return z;
		}

		public double ZOfRThetaSinSquared(double radius, double theta)
		{
			double fractionTDone = (radius - RMin) / (RMax - RMin);
			double t = fractionTDone * (TMax - TMin) + TMin;

			//double z = mSurface.Amplitude * Math.Cos(t);
			double sint = Math.Sin(t);
			double scale = 1 - fractionTDone * (1.0);
			double z = Amplitude * sint * sint * (scale);// *cost + cost;
			return z;
		}
        //double ZOfRTheta(double radius, double theta)
        //{
        //    double fractionDone = radius / (rMax - rMin); 

        //    //double tmax = 2*Math.PI;
        //    //double tmin = -2 * Math.PI;
        //    //double x = radius * Math.Cos(theta);
        //    //double y = radius * Math.Sin(theta);
        //    double t = fractionDone * (this.tMax - this.tMin) + this.tMin;
        //    double z = mAmp * Math.Cos(t);
        //    return z;
        //}
		public PolarWaveform()
		{
			renderer = PrimitiveManager.OnlyManager;
			mDevice = MainProgram.DxDevice;
			mZofR = ZOfRTheta0;
			InitializeMaterial();
			Register();
			CreateData();
		}

        public PolarWaveform(bool createData)
        {
			renderer = PrimitiveManager.OnlyManager;
			mDevice = MainProgram.DxDevice; 
			mZofR = ZOfRTheta0;

            InitializeMaterial();
           
            Register();

            if (createData)
			    CreateData();
        }
		public PolarWaveform(PolarRT_ZFunc zOfR, bool createData)
		{
			renderer = PrimitiveManager.OnlyManager;
			mDevice = MainProgram.DxDevice; 
			mZofR = zOfR;

			InitializeMaterial();

            Register();

			if (createData)
			{
				CreateData();
			}
		}

		public void CreateData()
        {
			CreateVertexData();
            VertexBufferChanged(this, mVertexData.Length); //as soon as its registered calls will probably start arriving.
			CreateIndexDataForShape();
			IndexBufferChanged(this, mIndexData.Length); //as soon as its registered calls will probably start arriving.
		}

		public void Recreate()
		{
			CreateData();
		}
		public void UpdateTransform()
		{
			VertexBufferChanged(this, mVertexData.Length); //as soon as its registered calls will probably start arriving.
		}
        ~PolarWaveform()
		{
			DisposeInstance();
		}

		public void DisposeInstance()
		{
			if (renderer != null)
				renderer.UnregisterPrimitive(this); //cannot hurt to unregister.
		}
		[PGBrowsable]
        public int RadialCount
        {
            get { return radialDivisions + 1; }
        }
		[PGBrowsable]
		public int AngleCount 
        {
            get { return angleDivisions + 1; }
        }
		int AngleIndexLast
        {
            get { return angleDivisions ; }
        }
		int RadialIndexLast
        {
            get { return radialDivisions ; }
        }

        //public void ReCreateVertexData()
        //{
        //    CreateVertexData(mZofR, true);  //data returned in vertexData
        //}
        public void Register()
        {
            PrimitiveManager.Register(this); 
			mRegistered = true;
        }

		//public SVector[,] AddData(SVector[,] rData)
		//{
		//    SVector[,] lData = this.mVertexMatrix;
		//    SVector[,] sums = null;

		//    if (rData.GetUpperBound(0) == lData.GetUpperBound(0))
		//    {
		//        if (rData.GetUpperBound(1) == lData.GetUpperBound(1))
		//        {

		//            sums = new SVector[lData.GetUpperBound(0), lData.GetUpperBound(1)];
		//            double dDThetaAngle = (angleMax - angleMin) / angleDivisions;
		//            double dRadius = (rMax - rMin) / radialDivisions;

		//            for (int angleEvalNo = 0; angleEvalNo <= AngleIndexLast; angleEvalNo++)
		//            {
		//                //double theta = angleEvalNo * dDThetaAngle;

		//                for (int radialEvalNo = RadialIndexLast; radialEvalNo >= 0; radialEvalNo--)//(double radius = rMin; radius <= rMax; radius += dRadius)
		//                {
		//                    SVector a = mVertexMatrix[angleEvalNo, radialEvalNo];
		//                    SVector b = rData[angleEvalNo, radialEvalNo];
		//                    this.mVertexMatrix[angleEvalNo, radialEvalNo] = new SVector(a.X + b.X, a.Y + b.Y, a.Z + b.Z, 0, 0, 0, a.Color);
                           
		//                }
		//            }
		//        }
		//    }

		//    return sums ;

		//}

		//public SVector[,] AddData(PolarWaveForm pwf)
		//{
		//    SVector[,] rData = pwf.mVertexMatrix;
		//    SVector[,] lData = this.mVertexMatrix;
		//    return (SVector[,]) AddData(rData);
		//}
		int stripVertices = 0;
        private void CreateIndexDataForShape()
		{
			stripVertices = 0;
	
			List<int> indices = new List<int>();
            //in this case (if used in PolarWaveform class),  vertexMatrix[0].Length == RadialCount
            
            //int angleUpperBound = mVertexMatrix.GetUpperBound(0);
            //int radialUpperBound = mVertexMatrix.GetUpperBound(1);
            //int idxArrayIdx = 0;

			double dDThetaAngle = (angleMax - angleMin) / (double) angleDivisions;
			double dRadius = (rMax - rMin) / (double)radialDivisions;

			for (int radialEvalNo = 0; radialEvalNo <= RadialIndexLast; radialEvalNo++)//(int radialEvalNo = RadialIndexLast; radialEvalNo > 0; radialEvalNo--)//(double radius = rMin; radius <= rMax; radius += dRadius)
			{ 
				for (int angleEvalNo = 0; angleEvalNo <= AngleCount; angleEvalNo++)
		        {
                    #region "Old code"
                    /*
                    int flatIndex1 = FlatIndexOf(radialEvalNo, angleEvalNo);
                    //if (angleEvalNo == AngleIndexLast - 1 && radialEvalNo == RadialIndexLast - 1)
                    //Debug.WriteLine("vtx[" + idxArrayIdx + "] = " + flatIndex1 + " => " + objVertices[mIndexData[flatIndex1]].ToString());
                    mIndexData[idxArrayIdx++] = flatIndex1;

                    int flatIndex2 = FlatIndexOf(radialEvalNo + 1, angleEvalNo);
                    //if (angleEvalNo == AngleIndexLast - 1 && radialEvalNo == RadialIndexLast - 1)
                    //Debug.WriteLine("vtx[" + idxArrayIdx + "] = " + flatIndex2 + " => " + objVertices[mIndexData[flatIndex2]].ToString());
                    mIndexData[idxArrayIdx++] = flatIndex2;

                    int flatIndex3 = FlatIndexOf(radialEvalNo, angleEvalNo + 1);
                    //if (angleEvalNo == AngleIndexLast - 1 && radialEvalNo == RadialIndexLast - 1)
                    //Debug.WriteLine("vtx[" + idxArrayIdx + "] = " + flatIndex3 + " => " + objVertices[mIndexData[flatIndex3]].ToString());
                    mIndexData[idxArrayIdx++] = flatIndex3;

                    int flatIndex4 = FlatIndexOf(radialEvalNo + 1, angleEvalNo + 1);
                    //if (angleEvalNo == AngleIndexLast - 1 && radialEvalNo == RadialIndexLast - 1)
                    //Debug.WriteLine("vtx[" + idxArrayIdx + "] = " + flatIndex4 + " => " + objVertices[mIndexData[flatIndex4]].ToString());
                    mIndexData[idxArrayIdx++] = flatIndex4;
                    */
                    #endregion "Old code"
				
					#if DEBUG_SHOW_CREATION
						Debug.WriteLine("Angle idx: " + angleEvalNo + ", Radial idx: " + radialEvalNo);
						double theta = (double)angleEvalNo*dDThetaAngle + angleMin;
						double cosTheta = Math.Cos(theta);
						double sinTheta = Math.Sin(theta);
						double radius = (double)radialEvalNo * dRadius + rMin;//(rMax - rMin) * radialEvalNo / RadialIndexLast + rMin;
						double x = radius * cosTheta;
						double y = radius * sinTheta;
						Debug.WriteLine(String.Format("a = {0}, x = {1}, y= {2}, r = {3}", theta, x, y, radius));

					#endif

					int flatIndex1, flatIndex2, flatIndex3, flatIndex4;

					flatIndex1 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo);
					indices.Add(flatIndex1);

#if DEBUG_SHOW_CREATION
					//Debug.WriteLine("idx = " + idxArrayIdx + "=> " + mIndexData[idxArrayIdx] + "; flatIndex1 = " + flatIndex1 + "=> " + mVertexData[flatIndex1]);
					Debug.WriteLine("flatIndex1 = " + flatIndex1 + "=> " + mVertexData[flatIndex1]);
#endif

					flatIndex2 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo - 1);
					//mIndexData[idxArrayIdx] = flatIndex2;
					indices.Add(flatIndex2);


					//flatIndex3 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo + 1);
					////mIndexData[idxArrayIdx] = flatIndex3;
					//indices.Add(flatIndex3);


					//flatIndex4 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo + 1);
					////mIndexData[idxArrayIdx] = flatIndex4;
					//indices.Add(flatIndex4);

					//flatIndex5 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo + 1);
					//indices.Add(flatIndex5);
					//idxArrayIdx++;

					//else
					//{
					//    int nextIndex1 = FlatVertexIndexFromEvalNo(angleEvalNo, radialEvalNo);
					//    indices.Add(nextIndex1);
					//    int nextIndex2 = FlatVertexIndexFromEvalNo(angleEvalNo - 1, radialEvalNo + 1);
					//    //mIndexData[idxArrayIdx] = flatIndex4;
					//    indices.Add(nextIndex2);

					//}

				}

				if (stripVertices == 0)
					stripVertices = indices.Count();
            }
			mIndexData = indices.ToArray();

            //DebugPrintDataIndices();
        }

        private void CreateVertexData( )
        {
            PolarRT_ZFunc PolarRT_ZFunc = mZofR;
            //for now until i know the total length i'm going to use a array of lists and convert to a 1 dimensional array.
            mVertexMatrix = new SVector[AngleCount , RadialCount];

            double dDThetaAngle = (angleMax - angleMin) / angleDivisions;
            double dRadius = (rMax-rMin ) / radialDivisions;
			for (int radialEvalNo = 0; radialEvalNo <= RadialIndexLast; radialEvalNo++)//(double radius = rMin; radius <= rMax; radius += dRadius)
            {
				double radius = (double)radialEvalNo * dRadius + rMin;//(rMax - rMin) * radialEvalNo / RadialIndexLast + rMin;

				for (int angleEvalNo = 0; angleEvalNo <= AngleIndexLast; angleEvalNo++)
				   	{
					double theta = (double)angleEvalNo * dDThetaAngle + angleMin;
					double cosTheta = Math.Cos(theta);
					double sinTheta = Math.Sin(theta);

				
                    double x = radius * cosTheta;
                    double y = radius * sinTheta;
					if (angleEvalNo == AngleIndexLast && radialEvalNo == RadialIndexLast)
					{
						Debug.WriteLine("At last point ");
					}

                    double z = PolarRT_ZFunc( radius, theta);//(radialEvalNo);
                    SVector cartesianPosition = new SVector((float)x, (float)y, (float)z, mVertexColor.ToArgb());

                    mVertexMatrix[angleEvalNo, radialEvalNo] = cartesianPosition;
                }
            }

            mVertexData = FlattenAngleCol(mVertexMatrix);

			#if DEBUG_SHOW_CREATION
				DebugPrintDataVertices();
			#endif
        }

        //assumes passed in [rnd][c] format.
        SVector[] FlattenAngleCol(SVector[,] vertexMatrix)
        {
            //in this case (if used in PolarWaveform class),  vertexMatrix[0].Length == RadialCount
            int angleUpperBound = vertexMatrix.GetUpperBound(0);
            int radialUpperBound = vertexMatrix.GetUpperBound(1);
			int radialEvalNo;
			int angleEvalNo;
            //Debug.Assert(angleUpperBound == AngleIndexLast + 1 && radialUpperBound == RadialIndexLast + 1);

            SVector[] aData = new SVector[(AngleCount) * (RadialCount)];

			int vertexIndex = 0;
			for (radialEvalNo = 0; radialEvalNo <= RadialIndexLast; radialEvalNo++)
			{
				for (angleEvalNo = 0; angleEvalNo <= AngleIndexLast; angleEvalNo++)
				{
                    SVector cartesianPosition = vertexMatrix[angleEvalNo, radialEvalNo];
					//int flatIndex = IndexOfVertex(radialEvalNo, angleEvalNo);
                    aData[vertexIndex] = cartesianPosition;
					vertexIndex++;
                }
            }
           return aData;                        
        }
        public void DebugPrintDataVertices()
        {
			int vertexIndex = 0;
			for (int radialEvalNo = 0; radialEvalNo <= RadialIndexLast; radialEvalNo++)//(double radius = rMin; radius <= rMax; radius += dRadius)
			{
				for (int angleEvalNo = 0; angleEvalNo <= AngleIndexLast; angleEvalNo++)
                {
                    Debug.WriteLine("vtxIdx= " + mVertexData[vertexIndex].ToString());
					vertexIndex++;
                }
            }
			Debug.WriteLine("Vertex count: " + mVertexData.Length);
        }

		public int FlatVertexIndexFromEvalNo(int angleEvalNo, int radialEvalNo)
		{
			int flatIndex = radialEvalNo * AngleCount + angleEvalNo;
			return flatIndex;
		}
		[PGBrowsable]
		public int NumberFaces
		{
			get { return max(RadialCount * (stripVertices - 2), 0) ; }
		}

		private int max(int p, int p_2)
		{
			return p >= p_2 ? p : p_2;
		}
         /// <summary>
        /// Since each primitiveWithVertices should for diversity be in charge of its own representation
        /// in rendering, although the memory will be managed.
        /// </summary>
        public void RenderIndexedPrimitives(PrimitiveManager caller, int vbDataLength, int ibLength)
        {
			for (int radialEvalNo = 0; radialEvalNo < RadialCount; radialEvalNo++)
			{
				//for (int radialIdx = 0; radialIdx < RadialCount; radialIdx++)
				//{
					//Debug.WriteLine("numVerticesPerRadius == flatIndexLength => ", (numVerticesPerRadius == flatIndexLength).ToString());          

					//index of first index
					//int indexStart =  4 * (AngleCount - 1) * radialIdx;
					//int vertexStart = radialIdx* AngleCount;
					//int numVerticesPerRadius = 2 * AngleCount;
					//int numPrimitivesPerRadius = 2 * AngleCount;

				mDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, stripVertices, 
					radialEvalNo * stripVertices, stripVertices - 2);
				//}
			}	
        }

        public void RenderPrimitives(PrimitiveManager renderer, int yourDataLength)
        {
            //draw just the vertex data as points
            mDevice.DrawPrimitives(PrimitiveType.PointList, 0, mVertexData.Length);            
        }
		[PGBrowsable]
        int IndicesCount
        {
            get { return mIndexData.Length; }
        }

		public Array GetIndices()
        {
			return (Array)mIndexData;
        }

        public void InitializeMaterial()
        {
			mMaterial = new Material();
			mMaterial.Specular = Color.FromArgb(122,255,255,255);
            mMaterial.Ambient = Color.FromArgb(122,255,255,255);
            mMaterial.Diffuse = Color.FromArgb(122,255,255,255);
        }
		[PGBrowsable]
        public Material Material
        {
			get { return mMaterial; }
			set { mMaterial = value; }
        }

        public void ManagerDestroyed(PrimitiveManager caller)
        {
            //signals to registered primitiveWithVertices generator that renderer is or has been destroyed
            //so we should stop sending messages or at least relaying them to the renderer
            renderer = null;
        }

		public Array GetVertices()
        {
			return (Array)mVertexData; //was SVector[]
        }        
        public int GetVerticesCount()
        {
            if (mVertexData == null)
                return 0;
            else
                return mVertexData.Length;
        }
        public int GetIndicesCount()
        {
            if (mIndexData == null)
                return 0;
            else
			    return mIndexData.Length;
        }

		#region IRangeableOld Members
		[PGBrowsable]
		public float XMin
		{
			get
			{
				return (float)-rMax;
			}
			set
			{
				//throw new NotImplementedException();
			}
		}
		[PGBrowsable]
		public float XMax
		{
			get
			{
				return (float)rMax;
			}
			set
			{
				//throw new NotImplementedException();
			}
		}
		[PGBrowsable]
		public float YMin
		{
			get
			{
				return -(float)rMax;
			}
			set
			{
				//throw new NotImplementedException();
			}
		}
		[PGBrowsable]
		public float YMax
		{
			get
			{
				return (float)rMax;
			}
			set
			{
				//throw new NotImplementedException();
			}
		}
		[PGBrowsable]
		public float ZMin
		{
			get
			{
				return -(float)rMax; 
			}
			set
			{
				//throw new NotImplementedException();
			}
		}
		[PGBrowsable]
		public float ZMax
		{
			get
			{
				return (float)rMax;
			}
			set
			{
				//throw new NotImplementedException();
			}
		}

		#endregion
	}

}
